import { DynamicModule, Global, Module, Provider } from '@nestjs/common'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { 
  ResourcesClientModuleAsyncOptions, 
  ResourcesClientModuleOptions, 
  ResourcesClientModuleOptionsFactory,
} from './interfaces/resources-client-options.interface'
import { RESOURCES_CLIENT, RESOURCES_CLIENT_OPTIONS } from './resources-client.constants'
import { ResourcesClientService } from './resources-client.service'

@Module({
  imports: [
    ClientsModule.registerAsync([
      {
        name: RESOURCES_CLIENT,
        inject: [ RESOURCES_CLIENT_OPTIONS ],
        useFactory: (options: ResourcesClientModuleOptions) => ({
          transport: Transport.NATS,
          options: {
            servers: options.natsServers,
          },
        }),
      },
    ]),
  ],
  providers: [ ResourcesClientService ],
  exports: [ ResourcesClientService ],
})
@Global()
export class ResourcesClientModule {
  public static forRoot(config: ResourcesClientModuleOptions): DynamicModule {
    return {
      module: ResourcesClientModule,
      providers: [
        {
          provide: RESOURCES_CLIENT_OPTIONS,
          useValue: config,
        },
      ],
      exports: [ RESOURCES_CLIENT_OPTIONS ],
    }
  }

  public static forRootAsync(options: ResourcesClientModuleAsyncOptions): DynamicModule {
    return {
      module: ResourcesClientModule,
      imports: options.imports,
      providers: this.createAsyncProviders(options),
      exports: [ RESOURCES_CLIENT_OPTIONS ],
    }
  }

  private static createAsyncProviders(options: ResourcesClientModuleAsyncOptions): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [ this.createAsyncOptionsProvider(options) ]
    }

    return [
      this.createAsyncOptionsProvider(options),
      {
        useClass: options.useClass!,
        provide: options.useClass!,
      },
    ]
  }

  private static createAsyncOptionsProvider(options: ResourcesClientModuleAsyncOptions): Provider {
    if (options.useFactory) {
      return {
        provide: RESOURCES_CLIENT_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      }
    }

    return {
      provide: RESOURCES_CLIENT_OPTIONS,
      useFactory: async (optionsFactory: ResourcesClientModuleOptionsFactory) => (
        await optionsFactory.createResourcesClientOptions()
      ),
      inject: [ (options.useExisting || options.useClass)! ],
    }
  }
}